package net;

import javax.swing.Timer;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import java.awt.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

import net.receiving.IncomingMessageFactory;
import net.message.processor.MessageProcessor;
import model.Model;
import model.SynchronizerListener;
import util.Config;

public class Synchronizer {

    private static int DELAY = Config.getInstance().getDelay();

    private static int TIME_LIMIT = Config.getInstance().getTimeLimit();

    private final Map<Integer, HashSet<Client>> images = new ConcurrentHashMap<Integer, HashSet<Client>>();

    private final Map<Client, Integer> otherClients = new ConcurrentHashMap<Client, Integer>();

    //private final Map<Integer, HashSet<Client>> myImageIDs = new ConcurrentHashMap<Integer, HashSet<Client>>();

    private final Sender sender;

    private final Receiver receiver;

    private final MessageProcessor processor;

    private final Timer timer = new Timer(DELAY, new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            sender.sendAlive();
            Set<Map.Entry<Client, Integer>> clients = otherClients.entrySet();
            for (Map.Entry<Client, Integer> client : clients) {
                if (client.getValue() == TIME_LIMIT) {
                    otherClients.remove(client.getKey());
                    removeClient(images, client.getKey());
                    for (Integer id: images.keySet()) {
                        if (images.get(id).isEmpty() /*&& !myImageIDs.containsKey(id)*/) {
                            //System.out.println("myImageIDs = " + myImageIDs);
                            model.removeImage(id);
                        }
                    }
                    //removeClient(myImageIDs, client.getKey());
                } else {
                    otherClients.put(client.getKey(), client.getValue() + 1);
                }
            }
        }
    });

    public Synchronizer(SendingMessageFactory cmFactory, IncomingMessageFactory imFactory, Model model) {
        this.model = model;
        model.setSynchronizerListener(new SynchronizerListener() {
            public void addImage(int imageID) {
                images.put(imageID, new HashSet<Client>());
                //myImageIDs.put(imageID, new HashSet<Client>());
                sender.sendCreateImage(imageID);
            }

            public void joinImage(int imageID) {
                if (!images.get(imageID).isEmpty()) {
                    sender.sendJoinImageRequest(imageID, images.get(imageID));
                }
            }

            public void imageChange(int imageID, int x, int y, int dx, int dy) {
                /*if (!myImageIDs.get(imageID).isEmpty()) {
                    sender.sendNewLine(imageID, myImageIDs.get(imageID), x, y, dx, dy, Color.BLACK);
                }*/
                sender.sendNewLine(imageID, images.get(imageID), x, y, dx, dy, Color.BLACK);
            }

            public void getImage(int imageID) {
                Synchronizer.this.getImage(imageID);
            }
        });
        sender = new Sender(cmFactory);
        processor = new MessageProcessor(this, sender);
        receiver = new Receiver(imFactory, processor);
    }

    private void removeClient(Map<Integer, HashSet<Client>> images, Client client) {
        Collection<HashSet<Client>> c = images.values();
        for (HashSet<Client> iclients : c) {
            synchronized (iclients) {
                iclients.remove(client);
            }
        }
    }

    private final Model model;

    public void start() {
        timer.start();
        new Thread(sender).start();
        sender.sendHelloRequest();
        new Thread(receiver).start();
    }

    /*public void stop() {
        sender.stop();
        receiver.stop();
        timer.stop();
    } */

    public void getImage(int imageID) {
        model.setImage(imageID, getImage(imageID, ImageMode.IMAGE));
    }

    private int[] getImage(int imageID, ImageMode mode) {
        ImageWrapper iw = new ImageWrapper(imageID, mode);
        sender.sendGetImageRequest(iw, images.get(imageID));
        return processor.getImage(imageID);
    }

    //---------------------------------------------------------//
    //-------  Those methods use other part of project  -------//

    /**
     * @return image or sketch specialized by <code>iw</code>
     */
    public int[] getMyImage(ImageWrapper iw) {
        switch (iw.mode) {
            case SKETCH:
                return model.getSketch(iw.imageID);
            case IMAGE:
                return model.getImage(iw.imageID);
            default:
                throw new RuntimeException();
        }
    }

    //-------------------------------------------------------------------//
    //  Those methods are used from Sendrer, Receiver, MessageProcessor  //

    /**
     * For processing of the messages JoinImageRequestMessage and JoinImageResponseMessage
     */
    public void addImageEditor(int imageID, Client client) {
        /*if (myImageIDs.containsKey(imageID)) {
            myImageIDs.get(imageID).add(client);
        } else {
            HashSet<Client> clients = new HashSet<Client>();
            clients.add(client);
            myImageIDs.put(imageID, clients);
        }*/
        if (images.containsKey(imageID)) {
            images.get(imageID).add(client);
        } else {
            HashSet<Client> clients = new HashSet<Client>();
            clients.add(client);
            images.put(imageID, clients);
        }
    }

    /**
     * For sending the HelloReponseMessage.
     *
     * @return IDs of all images edited by this cleint
     */
    public Set<Integer> getMyImageIDs() {
        //return myImageIDs.keySet();
        return images.keySet();
    }

    /**
     * Processes HelloResponseMessage.
     */
    public void setImageIDs(Client client, Set<Integer> imageIDs) {
        for (Integer image : imageIDs) {
            if (images.containsKey(image)) {
                images.get(image).add(client);
            } else {
                addImage(image, client);
            }
        }
    }

    public void drawLine(int imageID, int x, int y, int dx, int dy) {
        model.imageChangeFromSyncronizer(imageID, x, y, dx, dy);
    }

    public void addImage(final int imageID, Client client) {
        HashSet<Client> clients = new HashSet<Client>();
        clients.add(client);
        images.put(imageID, clients);
        new Thread(new Runnable() {
            public void run() {
                model.addSketch(imageID, getImage(imageID, ImageMode.SKETCH));
            }
        }).start();
    }

    public void confirm(Client client) {
        otherClients.put(client, 0);
    }

    //---------------------------------------------------------//

    public static enum ImageMode {
        IMAGE, SKETCH
    }

    public static class ImageWrapper {
        public final int imageID;

        public final ImageMode mode;

        public ImageWrapper(int imageID, ImageMode mode) {
            this.imageID = imageID;
            this.mode = mode;
        }

        public ImageWrapper(byte[] source) {
            imageID = Util.byteArrayToInt(source);
            mode = (source[4] == 0) ? ImageMode.IMAGE : ImageMode.SKETCH;
        }

        public byte[] toByteArray() {
            byte[] res = new byte[5];
            Util.intToByteArray(imageID, res, 0);
            res[4] = (mode == ImageMode.IMAGE) ? (byte) 0 : 1;
            return res;
        }

    }
}